namespace SimpleWebServices.Security.Principal
{
    using System;
    using System.Collections.ObjectModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Security.Principal;
    using System.Web;
    using System.Xml.Serialization;
    using SimpleWebServices.Data;
    using SimpleWebServices.Models;
    using SimpleWebServices.Web;

    [XmlRoot("user")]
    public abstract class User : Record, IIdentity, IPrincipal
    {
        protected User()
        {
            this.Email = new Email();
            this.Name = new PersonalName();
            this.Roles = new Collection<string>();
        }

        protected User(string authenticationType, bool isAuthenticated)
            : this()
        {
            this.IIdentityAuthenticationType = authenticationType;
            this.IIdentityIsAuthenticated = isAuthenticated;
        }

        [XmlElement("email")]
        public Email Email
        {
            get;
            set;
        }

        [XmlIgnore]
        public override string Entity
        {
            get
            {
                Entity value = new Entity();
                value.Append(this.Email);
                value.Append(this.Name);
                value.Append(this.Roles);

                return value;
            }
        }

        [XmlElement("name")]
        public PersonalName Name
        {
            get;
            set;
        }

        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Can't deserialize if the setter is private.")]
        [XmlArray("roles")]
        [XmlArrayItem("role")]
        public Collection<string> Roles
        {
            get;
            set;
        }

        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "Derived classes can change the protected IIdentityAuthenticationType property.")]
        string IIdentity.AuthenticationType
        {
            get
            {
                return this.IIdentityAuthenticationType;
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "Derived classes can change the protected IIdentityIsAuthenticated property.")]
        bool IIdentity.IsAuthenticated
        {
            get
            {
                return this.IIdentityIsAuthenticated;
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "Derived classes can override the protected GetIIdentityName() method.")]
        string IIdentity.Name
        {
            get
            {
                return this.GetIIdentityName();
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "Derived classes should not change this behaviour.")]
        IIdentity IPrincipal.Identity
        {
            get
            {
                return this;
            }
        }

        protected string IIdentityAuthenticationType
        {
            get;
            set;
        }

        protected bool IIdentityIsAuthenticated
        {
            get;
            set;
        }

        public static void SetFromHttpRequestCookie<T>()
            where T : User
        {
            IPrincipal current = HttpContextFactory.Current.User;
            if (null != current && !current.Identity.IsAuthenticated)
            {
                current = null;
            }

            User user = null;
            HttpCookie cookie = HttpContextFactory.Current.Request.Cookies["Authentication"];
            if (null != cookie)
            {
                AuthenticationRecord<T> auth = null;
                auth = Record.Retrieve<AuthenticationRecord<T>>((AbsoluteUri)cookie.Value);
                if (null != auth)
                {
                    user = auth.User.Get;
                    if (null != user)
                    {
                        user.IIdentityAuthenticationType = "Cookie";
                        user.IIdentityIsAuthenticated = true;
                    }
                }
            }

            HttpContextFactory.Current.User = (current ?? user) ?? new AnonymousUser();
        }

        public void ClearAuthenticationCookie<T>()
            where T : User
        {
            this.Store<T>();

            AbsoluteUri urn = AuthenticationRecord.FormatUrn(this.Token.Value);
            AuthenticationRecord<T> auth = AuthenticationRecord<T>.Retrieve<AuthenticationRecord<T>>(urn);
            if (null != auth)
            {
                Record.Remove<AuthenticationRecord<T>>(auth.Urn);
            }

            HttpContextFactory.Current.Response.Cookies.Add(new HttpCookie("Authentication", string.Empty)
            {
                Expires = DateTimeFactory.UtcNow.AddDays(-1)
            });
        }

        public void SetAuthenticationCookie<T>(bool session)
            where T : User
        {
            this.Store<T>();

            AbsoluteUri urn = AuthenticationRecord.FormatUrn(this.Token.Value);
            AuthenticationRecord<T> auth = AuthenticationRecord<T>.Retrieve<AuthenticationRecord<T>>(urn)
                ?? new AuthenticationRecord<T>
                {
                    Urn = urn
                };

            auth.User = new Link<T>(this.Urn);

            HttpContextFactory.Current.Response.Cookies.Add(auth.ToHttpCookie(session));
        }

        public override void Store<T>()
        {
            if (null == this.Urn)
            {
                this.Urn = "urn://user/" + Guid.NewGuid().ToString();
            }

            base.Store<T>();
        }

        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "Derived classes can override the protected IPrincipalIsInRole(string) method.")]
        bool IPrincipal.IsInRole(string role)
        {
            return this.IPrincipalIsInRole(role);
        }

        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "There is no setter functionality.")]
        protected virtual string GetIIdentityName()
        {
            return this.Email.Address;
        }

        protected virtual bool IPrincipalIsInRole(string role)
        {
            bool result = false;

            result = 0 != this.Roles.Where(x => string.Equals(x, role, StringComparison.OrdinalIgnoreCase)).Count();

            return result;
        }
    }
}